home *** CD-ROM | disk | FTP | other *** search
-
- #include <string.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <math.h>
-
- #include <OSUtils.h>
- #include <QuickDraw.h>
- #include <Sound.h>
- #include <SoundInput.h>
-
- #define NEED_MAC_STUFF 1
-
- #include "Ninkasi:C++ util:generic.h"
- #include "Ninkasi:C++ util:complete_window.h"
- #include "ear_defines.h"
- #include "ear_decl.h"
- #include "ear_prototypes.h"
-
- DEN_MOTHER_T interval_den_mother;
- void find_actual_sample(UBYTE_T **where,ULONG_T *nbytes,Handle snd_handle);
-
- extern double sin(),cos(),exp(),log(),fabs(),sqrt();
- void name_of_interval(char *name,int interval);
-
- #define MAX_SAMPLE_SIZE 100000
-
- #define MIN_THRESHOLD 7
- // average abs val of deviation from mean of their sampled singing
- // should be at least this much
-
- int
- get_period(
- unsigned char *data, //-- your data
- int n, //-- number of data points
- int lo_p, //-- shortest possible period
- int hi_p, //-- longest possible period
- short *grid, //-- storage you allocate for me (call me with n=0
- // to find out how many bytes)
- int grid_space, //-- number of bytes you allocated for me
- int *naverage, //-- number of periods averaged together to get this
- // result (output)
- short **nhit_ptr, //--pointer to the histogram; 256 bins per octave;
- // bin number=256*log2(p/lo_p)
- int *nhist, //--tells them how many bins in histogram,
- int **int_info_handle, //--Passes back a pointer to some handy data:
- // *int_info[0]=mean abs. val of deviation from mean
- // *int_info[1]=mean abs. val of derivative
- // (Means are only based on a subset of the
- // data.)
- // *int_info[2]=xshift
- // *int_info[3]=yshift
- // *int_info[4]=tstep
- double **double_info_handle
- //
- );
-
- int closest_integer(double x);
-
- void
- sing_interval_den_mother(complete_window *my_complete_window)
- {
- //--- decoder string for controls:
- static char *decoder_ptr =
-
- "\0request,result,play,record,go_on,t1,t2,t3";
- //--- has to begin with null so subroutines know it's not really
- // a Pascal string
-
- static char **decoder_string = &decoder_ptr;
-
- //--- keeping track of my current state:
- static int first_time = 1;
- static int they_have_interval_to_consider = 0;
-
- #define TYPICAL_TEMPO 105
- static double typical_tempo = TYPICAL_TEMPO;
- static int mean_note = 66;
- static int interval_enabled[MAX_INTERVAL+1];
- //--- about the current interval:
- static int note1,note2,duration,rest_between,interval,center,abs_i;
- static int lower_note = -999;
- static int higher_note = -999;
-
- //--- about their sampled singing:
- static int have_sample = 0;
- static OSErr snd_record_result;
- static double raw_sampling_freq = 22254.54545;
- static double sampling_freq;
- static int nbytes;
- static UBYTE_T *where_is_actual_sample;
- static int interval_they_sang;
- static double amount_sharp_or_flat;
- // The following are globals, so wrap_it_up() can deallocate the sample:
- // int have_sample_storage = 0;
- // static Handle sound_handle = (Handle) 0;
-
- static int have_analysis = 0;
- static int period;
- double freq,note;
- int *period_int_info;
- double *period_double_info;
- static int have_storage_for_get_period = 0;
- static short **get_period_storage_handle;
- static int size_of_get_period_storage;
- static short *period_hist;
- static int nperiod_hist;
- static int analysis_succeeded;
-
- int need_to_redraw,need_to_redraw_all,play_it,ready_to_update,record_it,
- redraw_request,they_want_to_record,just_gave_them_interval_to_consider,
- they_hit_go_on;
-
- static int difficulty_of_interval[MAX_INTERVAL+1]=
- {0, 1,1, 1,1, 1, 3,1, 2,2, 3,3, 0, 3,3, 3 };
- // 1 2 3 4 5 6 7 8 9 10
-
- static int difficulty = 3;
-
- static int need_to_analyze = 0;
- static int need_to_tell_them_what_they_sang = 0;
-
-
- char nifty_name[100];
- int nifty_index,recognized;
- GrafPtr save_graf;
- double tempo;
- int min_duration,max_duration;
-
-
- need_to_redraw = 0;
- need_to_redraw_all = 0;
- redraw_request = 0;
- play_it = 0;
- they_want_to_record = 0;
- just_gave_them_interval_to_consider = 0;
- they_hit_go_on = 0;
-
- if (first_time) {
- first_time = 0;
- for (int i=0; i<=MAX_INTERVAL; i++) {
- interval_enabled[i] = (i!=0) && (i<=12);
- }
- }
-
- switch(my_complete_window->whassup) {
- case complete_window_created:
- need_to_redraw = 1;
- need_to_redraw_all = 1;
- break;
- case complete_window_redraw:
- need_to_redraw = 1;
- need_to_redraw_all = 1;
- ready_to_update = 1; //-- main program does begin update & sets graf port
- break;
- case complete_window_action:
- part_number_to_nifty_label(nifty_name,&nifty_index,
- *decoder_string,my_complete_window->part_number);
- if (nifty_index != -999) {
- recognized = 0;
- if (strcmp(nifty_name,"play")==0) {
- play_it = 1;
- }
- if (strcmp(nifty_name,"record")==0) {
- they_want_to_record = 1;
- }
- if (strcmp(nifty_name,"go_on")==0) {
- they_hit_go_on = 1;
- }
- }//--end if they hit a valid control
- break;
- case complete_window_erase:
- they_have_interval_to_consider = 0;
- first_time = 1;
- sing_interval_window_exists = 0;
- if (have_sample_storage) {
- if (VALID_HANDLE(sound_handle)) {
- HUnlock(sound_handle);
- DisposHandle(sound_handle);
- }
- have_sample_storage = 0;
- }
- return;
- }
-
- if (they_hit_go_on)
- they_have_interval_to_consider = 0;
-
- if (!they_have_interval_to_consider) {
- int previous_lower,previous_higher;
- previous_lower = lower_note;
- previous_higher = higher_note;
- min_duration = .7 * 2000.*60./typical_tempo;
- max_duration = 4 * 2000.*60./typical_tempo;
- do {
- tempo = typical_tempo*(1.+.2*(random_double()+random_double()-1.));
- duration = 2000.*60./tempo;
- } while (duration>max_duration || duration<min_duration);
- if (difficulty<1) difficulty=1; // avoid infinite loop...
- do {
- abs_i = random_integer(MAX_INTERVAL+1)-1;
- if (random_double()<.5)
- interval = abs_i;
- else
- interval = -abs_i;
- center = mean_note+(random_double()-.5)*8+(random_double()-.5)*8;
- /* mean_note plus or minus an something, biased towards
- pitches close to mean_note */
- lower_note = center-interval/2;
- higher_note = lower_note+interval;
- if (interval<0) {
- note1 = higher_note;
- note2 = lower_note;
- }
- else {
- note1 = lower_note;
- note2 = higher_note;
- }
- } while (random_integer(difficulty)<difficulty_of_interval[abs_i]
- || (previous_lower==lower_note && previous_higher==higher_note));
- play_it = 1;
- they_have_interval_to_consider = 1;
- just_gave_them_interval_to_consider = 1;
- need_to_redraw = 1;
- redraw_request = 1;
- have_analysis = 0;
- }//---end if need to choose new interval
-
- //--- This is the first block of drawing code, including the code
- // that tells them what interval to sing. There is a second block,
- // which includes the code that tells them what they actually sang.
- if (need_to_redraw) {
- if (!ready_to_update) {
- GetPort(&save_graf);
- SetPort(my_complete_window->the_window);
- }
- if (need_to_redraw_all) {
- normal_text_style();
- refresh_text(my_complete_window,"t1",1,*decoder_string);
- refresh_text(my_complete_window,"t2",1,*decoder_string);
- TextFont((short) newYork);
- TextSize((short) 10);
- refresh_text(my_complete_window,"t3",1,*decoder_string);
- normal_text_style();
- }
- if (redraw_request || need_to_redraw_all) {
- Handle h;
- char s[50],interval_name[30];
- int i,tot;
- get_item_by_nifty_label(my_complete_window->the_window,
- "request",1,*decoder_string,&h);
- if (VALID_HANDLE(h)) {
- name_of_interval(interval_name,abs_i);
- if (abs_i != 0)
- sprintf(s+1,"%s %s",(interval<0 ? "descending" : "ascending"),
- interval_name);
- else
- sprintf(s+1,"%s",interval_name);
- s[0] = strlen(s+1);
- TextFont((short) newYork);
- SetIText(h,(unsigned char *) s);
- normal_text_style();
- }
- }
- if (!ready_to_update) {
- SetPort(save_graf);
- }
- } //---end if need to redraw
-
-
- if (play_it) {
- int notes[4];
- notes[0] = note1;
- play_chord(1,my_snd_chan,notes,duration,1);
- }
-
- if (they_want_to_record
- // || just_gave_them_interval_to_consider
- // ...made it impossible to get out of modal dialog
- ) {
- Point corner;
- ULONG_T i;
- if (have_sample_storage) {
- if (VALID_HANDLE(sound_handle)) {
- HUnlock(sound_handle);
- DisposHandle(sound_handle);
- }
- have_sample_storage = 0;
- }
- sound_handle = NewHandle((Size) MAX_SAMPLE_SIZE);
- if (sound_handle == (Handle) 0) {
- static char s[75];
- sprintf(s,"Out of memory, recording sound, error=%d",
- (int) MemError());
- bail_out(s);
- }
- else
- have_sample_storage = 1;
- SetPt(&corner,300,100);
- HUnlock(sound_handle);
- snd_record_result = SndRecord((ModalFilterProcPtr) 0,corner,siBestQuality,&sound_handle);
- HLock(sound_handle);
- if (snd_record_result == userCanceledErr) SysBeep((short) 0);
- if (snd_record_result==0) {
- ULONG_T nbytes_raw;
- have_sample = 1;
- find_actual_sample(&where_is_actual_sample,&nbytes_raw,sound_handle);
-
- if (0) { //---- test with sine wave
- int i;
- watch_cursor();
- for (i=0; i<nbytes_raw; i++)
- where_is_actual_sample[i] = 128+60*sin(.4+2.*3.1416*440./raw_sampling_freq*i);
- arrow_cursor();
- }
- if (0) { //---- test with zero
- int i;
- for (i=0; i<nbytes_raw; i++)
- where_is_actual_sample[i] = 128;
- }
-
- nbytes = nbytes_raw;
- sampling_freq = raw_sampling_freq;
-
- }
- if (have_sample) {
- need_to_analyze = 1;
- need_to_tell_them_what_they_sang = 1;
- need_to_redraw = 1;
- }
- }
-
- if (need_to_analyze && have_sample) {
- int lo_p,hi_p,naverage;
- if (!have_storage_for_get_period) {
- size_of_get_period_storage = get_period((unsigned char *) 0,
- 0,0,0,(short *) 0,0,(int *) 0,&period_hist,&nperiod_hist,
- (int **) 0,(double **) 0);
- get_period_storage_handle = (short **) NewHandle((Size) size_of_get_period_storage);
- if (VALID_HANDLE(get_period_storage_handle)) {
- have_storage_for_get_period = 1;
- }
- else
- bail_out("out of memory");
- }
- lo_p = 20;
- hi_p = 300;
- watch_cursor();
- period = get_period(where_is_actual_sample,(int) nbytes,
- lo_p,hi_p,
- (short *) *get_period_storage_handle,
- size_of_get_period_storage,
- &naverage,&period_hist,&nperiod_hist,
- &period_int_info,&period_double_info);
- arrow_cursor();
- analysis_succeeded =
- (period!=0) && (period_int_info[0]>=MIN_THRESHOLD);
- if (analysis_succeeded) {
- freq = 256.*raw_sampling_freq/period;
- note = 60.+12.*log(freq/261.6)/log(2.);
- interval_they_sang = closest_integer(note-note1);
- amount_sharp_or_flat = note-note1-interval_they_sang;
- }
- have_analysis = analysis_succeeded;
- need_to_analyze = 0;
- }
-
- //--- This is the second block of drawing code, including the code
- // that tells them what they actually sang.
- if (need_to_redraw) {
- if (!ready_to_update) {
- GetPort(&save_graf);
- SetPort(my_complete_window->the_window);
- }
- if (need_to_tell_them_what_they_sang || need_to_redraw_all) {
- Handle h;
- char s[300],s2[300],interval_name[300],inverted_interval_name[300];
- int i,tot;
- get_item_by_nifty_label(my_complete_window->the_window,
- "result",1,*decoder_string,&h);
- if (VALID_HANDLE(h)) {
- if (have_sample) {
- if (have_analysis) {
- name_of_interval(interval_name,interval_they_sang);
- // If they sang an interval in the opposite direction to what
- // was requested, maybe they sang the right note, but displaced
- // an octave. Tell them what the inversion would be:
- if (interval*interval_they_sang<0) {
- name_of_interval(inverted_interval_name,
- inversion_of_interval(interval_they_sang));
- sprintf(s2," (inversion would be %s)",inverted_interval_name);
- }
- else
- sprintf(s2,"");
- sprintf(s+1,"%s %s, %4.1lf semitones %s%s",
- (interval_they_sang==0 ? "" :
- (interval_they_sang<0 ? "descending" : "ascending")),
- interval_name,
- fabs(amount_sharp_or_flat),
- (amount_sharp_or_flat>0 ? "sharp" : "flat"),
- s2
-
- /*
- (int) period,(double) freq,(int) note1,(double) note,
-
- period_int_info[0],period_int_info[1],
- period_int_info[2],period_int_info[3],
- period_int_info[4]
-
- */
-
- );
- need_to_tell_them_what_they_sang = 0;
- }
- else {
- if (period_int_info[0]<1)
- sprintf(s+1,"No sound was detected. You may not have a microphone installed.");
- else
- sprintf(s+1,"Try singing louder, or closer to the microphone.");
- }
- }
- else
- sprintf(s+1,""); //-- don't have any analysis, haven't recorded a sample
- s[0] = strlen(s+1);
- TextFont((short) newYork);
- SetIText(h,(unsigned char *) s);
- normal_text_style();
- }
- }
- if (!ready_to_update) {
- SetPort(save_graf);
- }
- } //---end if need to redraw
-
-
- }
-
- int
- closest_integer(double x)
- {
- int y;
- y = x+.5;
- while(fabs(y+1.-x)<fabs(y-x)) ++y;
- while(fabs(y-1.-x)<fabs(y-x)) --y;
- return y;
- }
-
- void
- name_of_interval(char *interval_name,int interval)
- {
- int abs_i,octave_bump;
- abs_i = interval;
- if (abs_i<0) abs_i = -abs_i;
- octave_bump = 0;
- if (abs_i>15) {
- while(abs_i>=12) {
- abs_i -= 12;
- ++octave_bump;
- }
- }
- switch(abs_i) {
- case 0: strcpy(interval_name,"unison"); break;
- case 1: strcpy(interval_name,"b2"); break;
- case 2: strcpy(interval_name,"2"); break;
- case 3: strcpy(interval_name,"b3"); break;
- case 4: strcpy(interval_name,"3"); break;
- case 5: strcpy(interval_name,"4"); break;
- case 6: strcpy(interval_name,"b5"); break;
- case 7: strcpy(interval_name,"5"); break;
- case 8: strcpy(interval_name,"b6"); break;
- case 9: strcpy(interval_name,"6"); break;
- case 10: strcpy(interval_name,"b7"); break;
- case 11: strcpy(interval_name,"7"); break;
- case 12: strcpy(interval_name,"octave"); break;
- case 13: strcpy(interval_name,"b9"); break;
- case 14: strcpy(interval_name,"9"); break;
- case 15: strcpy(interval_name,"b10"); break;
- default: strcpy(interval_name,"--"); break;
- }
-
- if (octave_bump>0) {
- char s[100];
- switch(octave_bump) {
- case 1:
- strcat(interval_name," + octave");
- break;
- default:
- sprintf(s," + %d octaves",octave_bump);
- strcat(interval_name,s);
- break;
- }
- }
- }
-
- #define UBYTE_AT(x) (* (UBYTE_T *) (x))
- #define UWORD_AT(x) (* (UWORD_T *) (x))
- #define ULONG_AT(x) (* (ULONG_T *) (x))
-
-
- // Returns the address of the actual sound data, yes the actual honest
- // to god bytes, not the header in front or the header in front of the
- // header. If there's more than one sound command, will only return
- // the data correspinding to the first one.
- // Although we don't move memory in here, it's probably safest to
- // lock the sample before calling this routine.
- void
- find_actual_sample(UBYTE_T **where,ULONG_T *nbytes,Handle snd_handle)
- {
- UWORD_T nsynth,ncmds,cmd;
- ULONG_T par2;
- UBYTE_T *find_it;
- UBYTE_T *snd;
- snd = * (UBYTE_T **) snd_handle;
- nsynth = UWORD_AT(snd+2);
- ncmds = UWORD_AT(snd+4+nsynth*2+4);
- cmd = UWORD_AT(snd+4+nsynth*2+4+2);
- par2 = ULONG_AT(snd+4+nsynth*2+4+2+4);
- if (cmd & dataOffsetFlag)
- find_it = snd+par2;
- else
- find_it = * (UBYTE_T **) &par2;
- find_it += 4; //---- THINK Reference seems to leave out a longword,
- // so bump past it!?!?
- *nbytes = ULONG_AT(find_it);
- *where = find_it + 4*4+2;
- }
-
-